#!/usr/bin/env bash
#
# 文 件 名: multi-install-mysql.sh
# 脚本功能: 在基于 amd64/arm64 架构的 linux 上部署 MySQL 数据库
# 文件编码: UTF-8
# 编程语言: Shell Script(Bash 4.x/5.x)
# 编程工具: VSCode/Goland
# 显示语言: 简体中文
# 脚本编写：杨芳超
# 联系邮箱: i-up@qq.com
# 问题反馈: https://seeyon.ren
#

# mysql 5.7.x EOL: 2023-10-21

# ******************** 按需配置以下参数 ******************** #

# 软件安装 '根' 目录
installRootDir="/apps"

# 是否载入脚本所在目录下的bin目录 1=加载
# 此参数主要用于老系统的bash版本过低时 使用预置的命令文件来运行 仅虚拟化平台 物理机可能会报段错误
loadsBinDir="1"
# 是否加载 /etc/profile 环境变量文件 1=加载
loadEtcProfile=""

# 如果已存在同名安装目录 是否开启备份 1=开启 只支持 tgz|rar|zip 格式 备份目录默认为脚本所在平级的 backup 目录
# 建议开启此参数 防止误删 脚本未做人工选择提示 将直接执行任务
# 注意: 此备份是备份 脚本设定的 mysql 的安装文件夹 不是数据库数据的导出导入那种备份
enableBackup="1"
# 备份文件保存目录 默认保存在脚本所在目录下的backup下 若需修改 请设置为绝对路径
backupDir="$(dirname "$(readlink -f "$0")")/backup"
# 备份文件格式(后缀名) 只能是 tgz|rar|zip|7z 没有对应备份程序时 可尝试使用不同的备份格式 如果使用 tgz 一般情况下 只需要一个脚本即可
backupFormat="tgz"
# 客户端和服务端 当前设置为相同 端口默认为 3306 在同一台主机安装多个版本的时候 注意修改端口号和安装路径以及启动控制文件
dbPort="3306"
# 字符集设置(仅字符集 不设置字符序) 如果设置为 utf8mb4 请使用 mysql 5.7/8.0
dbCharacter="utf8mb4"
# 默认存储引擎
dbEngine="InnoDB"
# 初始随机密码位数 默认为 10 位 10 <= dbPWDDigit < 30
dbPWDDigit="10"
# 身份认证插件 仅安装 MySQL 8.x 时按需设置 caching_sha2_password | mysql_native_password | sha256_password
# 8.0.27 开始使用 authentication_policy 并将要废弃 default_authentication_plugin
dbDefaultAuthPlugin="caching_sha2_password"
# root 账号默认身份认证方法 仅安装 MySQL 8.x 时按需设置
rootAuthPlugin="caching_sha2_password"
# 数据库最大连接数
dbMaxConnect="600"

# max_allowed_packet 参数值大小 单位MB 此参数服务端和客户端都有
# 当迁移/升级OA数据库数据时 怀疑 blob 过大导致异常时 可考虑在文件 bin/mysqld_safe 中添加: ulimit -d 256000 并重启 mysqld 服务
# https://dev.mysql.com/doc/refman/8.0/en/packet-too-large.html
dbMaxAllowedPacket="256"

# innodb_buffer_pool_size 参数值占用的的内存百分比 脚本将根据这里指定的百分比进行计算和取整赋值
# 计算公式为 总内存大小(GB)*1024*内存百分比=innodb_buffer_pool_size(MB)
# 需指定为1位数的小数点 如 0.6 表示 60% 即 innodb_buffer_pool_size 的值 = 物理内存的 60%
dbIBPSizeUseMemPercent="0.4"

# 创建数据库 不需创建则双引号内保留为空 数据库名不要使用空格等特殊字符 正确样例: createDBName="a8v81"
createDBName=""

# MySQL 8.0.x 时 为了避免 binlog 撑爆磁盘 设置清除过期日志的时间 以秒为单位
dbBinLogExpireLimit="86400"

# MySQL 默认时区 国内用户使用 8.0+ 时需要调整此参数
dbDefaultTimeZone="+8:00"

# MySQL 事务隔离级别 READ-UNCOMMITTED | READ-COMMITTED | REPEATABLE-READ | SERIALIZABLE
dbTransactionIsolation="READ-COMMITTED"

# innodb 数据文件及 redo log 的打开、刷写模式 只能是 fdatasync(默认) | O_DSYNC | O_DIRECT
# fdatasync，调用 fsync() 去刷数据文件与 redo log 的 buffer
# O_DSYNC innodb 会使用 O_SYNC 方式打开和刷写 redo log 使用 fsync() 刷写数据文件
# O_DIRECT innodb 使用 O_DIRECT 打开数据文件 使用 fsync() 刷写数据文件跟 redo log
innodbFlushMethod="O_DIRECT"

# innodb_page_size 大小值(单位为K) 官方默认是16k 最好是根据磁盘系统和性能来设置
innodbPageSize="16"

# slow_query_log 根据需要设置开启慢查询日志 只能是 ON|OFF 其中 ON=开启 OFF=关闭
slowQueryLog="OFF"

# 是否加入开机自启动 1=启用 注意: 仅支持 systemd 服务管理方式
enableAutostart="1"
# 安装后是否启动服务 1=启动 仅限于新装
startAppSrv="1"

# 是否写入 PATH 系统环境变量 1="写入"
enableWriteEnvPATH="1"
# 是否写入 LD_LIBRARY_PATH 系统环境变量 1=写入
enableWriteEnvLD=""
# 系统环境变量配置文件
sysEnvFile="/etc/profile"

# 防火墙设置 取值只能是 dis|add 当取值为 dis 时直接禁用防火墙 取值为 add 时 添加数据库的端口开放规则到防火墙中
firewallMethod="add"

# **** 以下参数不建议修改 **** #
appInstallDir="${installRootDir}/mysql"
appBinDir="${installRootDir}/mysql/bin"
appLogDir="${installRootDir}/mysql/logs"
appDataDir="${installRootDir}/mysql/data"
appLibDir="${installRootDir}/mysql/lib"
appPidFile="${appDataDir}/mysqld.pid"
appSbinFile="${appBinDir}/mysqld"
appBinFile="${appBinDir}/mysql"
appUpgradeFile="${appBinDir}/mysql_upgrade"
appAdminBinFile="${appBinDir}/mysqladmin"
# mysql 配置文件
appEtcFile="${appInstallDir}/my.cnf"
# mysql 错误日志文件
appErrLogFile="${appLogDir}/error.log"
sockFileServer="${appDataDir}/mysql.sock"
sockFileClient="${appDataDir}/mysql.sock"
sockFileMysql="${appDataDir}/mysql.sock"
# appEnvFile="${appInstallDir}/support-files/mysql.env"
appSrvFile="${appInstallDir}/support-files/mysql.server"
# 服务控制文件 - systemd
sysSrvFile="/etc/systemd/system/mysqld.service"
# **** 以上参数不建议修改 **** #

# ******************** 按需配置以上参数 ******************** #

# 自定义颜色函数
function oput() {
    # 功能: 自定义颜色并打印标准输出
    # 例子: oput red "系统依赖检查未通过"
    local red_color green_color yellow_color blue_color pink_color white_blue down_blue flash_red res
    # 红色
    red_color='\E[1;31m'
    # 绿色
    green_color='\E[1;32m'
    # 黄色
    yellow_color='\E[1;33m'
    # 蓝色
    blue_color='\E[1;34m'
    # 紫色
    pink_color='\E[1;35m'
    # 白底蓝字
    white_blue='\E[47;34m'
    # 下划线+蓝色
    down_blue='\E[4;36m'
    # 红闪
    flash_red='\E[5;31m'
    # 重置
    res='\E[0m'

    # 限制传入参数必须为2个 否则退出
    if [ $# -ne 2 ]; then echo "Usage $0 {red|yellow|blue|green|pink|wb|db|fr} content"; exit; fi
    case "$1" in
        red | RED ) echo -e "${red_color}$2${res}"; ;;
        yellow | YELLOW ) echo -e "${yellow_color}$2${res}"; ;;
        green | GREEN ) echo -e "${green_color}$2${res}"; ;;
        blue | BLUE ) echo -e "${blue_color}$2${res}"; ;;
        pink | PINK ) echo -e "${pink_color}$2${res}"; ;;
        wb | WB ) echo -e "${white_blue}$2${res}"; ;;
        db | DB ) echo -e "${down_blue}$2${res}"; ;;
        fr | FR ) echo -e "${flash_red}$2${res}"; ;;
        * ) echo -e "请指定一个颜色代码：{red|yellow|blue|green|pink|wb|db|fr}"; ;;
    esac
}

# 语言环境
export LANG=zh_CN.UTF-8
# PATH设置
export PATH=/sbin:/bin:/usr/sbin:/usr/bin
# 脚本目录 -- 与工作目录有区别
sDir="$(dirname "$(readlink -f "$0")")"
if ! cd "${sDir}"; then oput red "目录切换: 失败 ${sDir}" && exit; fi
# 脚本文件
sFile="$(basename "$0")"
# 脚本功能
sDesc="在基于 amd64/arm64 架构的 linux 上部署 MySQL 数据库"
# 系统环境
sEnv="AMD64/ARM64 + Linux + Bash 4.x/5.x"
# Bin 目录
sBinDir="${sDir}/bin"
# Log 目录
sLogDir="${sDir}/logs"
if ! mkdir -p "${sLogDir}"; then echo "Failed to create script log directory" && exit; fi
# Log 文件
sLogFile="${sLogDir}/script.log"
# 脚本用法
sUsage="bash|bin/bash ${sFile} [install|remove|backup|depend]"
# 创建时间
# sCreate="2014-06-02 08:41:38"
# 更新时间
sUpdate="2024-05-05 10:35:51"
# 更新内容
## 1、取消 8.1/8.2/8.3 的版本支持
## 2、新增 8.4 的版本支持

# 操作系统
OS="$(uname | tr '[:upper:]' '[:lower:]')"
if [ "${OS}" != "linux" ]; then echo "${OS} is not supported by this script" && exit; fi

# 平台架构
archType="$(uname -m)"
case "${archType}" in
     "amd64" | "x86_64" | "arm64" | "aarch64" )
        ;;
    * )
        echo; oput red "${archType} is not supported by this script"; echo; exit
        ;;
esac

# 账号权限
if [ "$(id -un)" != "root" ]; then echo; oput red "please use 'sudo bash ${sFile}' to run script"; echo && exit; fi
# bash版本
if ((BASH_VERSINFO[0] < 4)); then oput red "sorry, you need bash 4.0 or newer to run this script."; exit 1; fi

# 服务管理 - 脚本设定只支持使用 systemd 进行服务管理
if command -v systemctl &>/dev/null; then
    # initIs 表示 Linux 系统的 init 分类
    initIs="systemd"
    # srvCtlCMD 服务管理控制命令
    srvCtlCMD="systemctl"
else
    initIs="unkown"
    srvCtlCMD="unkown"
fi

# 生成并清空脚本运行日志
touch "${sLogFile}" && true >"${sLogFile}"
# 写入当前系统时间和脚本文件名到脚本日志
(echo; echo "$(date '+%Y-%m-%d %H:%M:%S') from ${sDir}/${sFile}") >>"${sLogFile}"

# IP地址 多IP时 只取第一个IP 自动获取 如有需要请手动指定 如 hostAddr="192.168.100.111"
hostAddr="$(ip addr | grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' | grep -Ev '127.0.0|0.0.0|.255$' | head -1)"
# 客户端IP地址 - 自动获取
clientAddr="$(who -u am i 2>/dev/null | awk '{print $NF}'| sed -e 's/[a-zA-Z:()]//g')"
if [ "${clientAddr}" = "" ]; then clientAddr="${hostAddr}"; fi

# OS 家族判断
if command -v yum &>/dev/null; then
    osFamily="redhat"
    osDCMD="yum"
    # osPCMD="rpm"
elif command -v apt &>/dev/null; then
    osFamily="debian"
    osDCMD="apt"
    # osPCMD="dpkg"
elif command -v zypper &>/dev/null; then
    osFamily="suse"
    osDCMD="zypper"
    # osPCMD="rpm"
# 如果要在没有使用 yum/apt/zypper 这三个依赖包管理器的系统上安装 请注释掉以下两行
else
    echo; oput red "the current $(uname) is not supported"; echo; exit
fi

# 打印脚本信息
function print_parameter_info() {
    echo "软件安装目录: ${installRootDir}"
    echo "软件备份目录: ${backupDir}"
    echo "备份文件格式: ${backupFormat}"
    if [ "${enableBackup}" = "1" ]; then
        echo "软件自动备份: 已启用"
    else
        echo "软件自动备份: 未启用"
    fi

    echo -e "系统环境变量: \c"
    if [ "${enableWriteEnvPATH}" = "1" ]; then
        echo -e "写入 PATH 到 ${sysEnvFile}; \c"
    else
        echo -e "不写入 PATH; \c"
    fi
    if [ "${enableWriteEnvLD}" = "1" ]; then
        echo "写入 LD_LIBRARY_PATH 到 ${sysEnvFile}"
    else
        echo "不写入 LD_LIBRARY_PATH"
    fi
    if [ "${enableAutostart}" = "1" ]; then
        echo "设置开机自启: 是"
    else
        echo "设置开机自启: 否"
    fi
    if [ "${startAppSrv}" = "1" ]; then
        echo "装完启动服务: 是"
    else
        echo "装完启动服务: 否"
    fi

    echo "服务管理系统: ${initIs}"
    echo "服务控制命令: ${srvCtlCMD}"

    if [ "${createDBName}" != "" ]; then echo "创建新数据库: ${createDBName}"; fi

    case "${firewallMethod}" in
        "add" )
            echo "防 火 墙策略: 开放数据库端口"
            ;;
        "dis" )
            echo "防 火 墙策略: 关闭且禁止开机自启"
            ;;
        * )
            echo "防 火 墙策略: 不做任何操作"
            ;;
    esac
}

# 脚本参数校验
function verify_scriptPara() {
    if [ "${installRootDir}" = "" ]; then
        oput red "未设置软件安装根目录" && return 1
    elif ! echo "${installRootDir}" | grep -Eq '^/[a-zA-Z]'; then
        oput red "需要设置软件安装根目录为绝对路径: 例如 /apps" && return 1
    fi
    if [ ! -d "${installRootDir}" ]; then
        echo -e "创建目录: ${installRootDir} \c"
        if ! mkdir -p "${installRootDir}"; then
            oput red "失败" && return 1
        else
            oput green "成功"
            chmod -R 755 "${installRootDir}"
        fi
    fi

    # 如果开启备份 则创建备份目录
    if [ "${enableBackup}" = "1" ]; then mkdir -p "${backupDir}"; fi
    case "${backupFormat}" in
        "tgz" )
            packProg="tar"
            ;;
        "rar" )
            if [ "${osGlibcMinorVer}" -le 12 ] ; then
                packProg="rar_static"
            else
                packProg="rar"
            fi
            ;;
        "zip" )
            packProg="zip"
            ;;
        "7z" )
            if [ "${osGlibcMinorVer}" -lt 14 ] ; then
                packProg=""
                echo; oput red "备份格式: 不支持在当前操作系统上使用 ${backupFormat} 备份文件格式"; echo && return 1
            else
                packProg="7zzs"
            fi
            ;;
        * )
            echo; oput red "备份格式: 只支持 tgz/rar/zip/7z"; echo && return 1
            ;;
    esac

    if ! echo "${dbIBPSizeUseMemPercent}" | grep -Eq '^0.[1-9]$'; then
        oput red "参数有误: dbIBPSizeUseMemPercent 的值必须是 0. 开头且只保留 1 位小数点的浮点数 如 0.6"
        return 1
    fi
}

# 检查脚本所需命令
function check_CMD() {
    local CMDLists=(getconf pgrep ss tee)
    if [ "${enableBackup}" = "1" ]; then
        case "${backupFormat}" in
            "tgz" )
                CMDLists+=("${packProg}" "gzip")
                ;;
            * )
                CMDLists+=("${packProg}")
                ;;
        esac
    fi
    local CMD
    for CMD in "${CMDLists[@]}"; do
        if ! command -v "${CMD}" &>/dev/null; then
            main_prompt; echo
            case "${CMD}" in
                "tar" )
                    echo "$(oput red "${CMD}") 命令未找到 安装命令: $(oput blue "${osDCMD} install -y ${CMD}")"
                    ;;
                "zip" )
                    echo "$(oput red "${CMD}") 命令未找到 安装命令: $(oput blue "${osDCMD} install -y ${CMD}")"
                    ;;
                "rar" | "rar_static" )
                    case "${archType}" in
                        "x86_64" | "amd64" )
                            echo "$(oput red "${CMD}") 命令未找到 可使用脚本安装 $(oput blue "multi-install-rar.sh")"
                            ;;
                        * )
                            oput red "当前 ${archType} 架构不支持使用 ${CMD} 进行备份压缩 请调整备份压缩格式参数"
                            ;;
                    esac
                    ;;
                "7zzs" )
                    echo "$(oput red "${CMD}") 命令未找到 安装命令: $(oput blue "${osDCMD} install -y ${CMD}")"
                    ;;
                "tee" )
                    echo "$(oput red "${CMD}") 命令未找到 安装命令: $(oput blue "${osDCMD} install -y coreutils")"
                    ;;
                "getconf" )
                    echo "$(oput red "${CMD}") 命令未找到 安装命令: $(oput blue "${osDCMD} install -y glibc-common")"
                    ;;
                "pgrep" )
                    echo "$(oput red "${CMD}") 命令未找到 安装命令: $(oput blue "${osDCMD} install -y procps")"
                    ;;
                "ss" )
                    echo "$(oput red "${CMD}") 命令未找到 安装命令: $(oput blue "${osDCMD} install -y iproute")"
                    ;;
                * )
                    echo "$(oput red "${CMD}") 命令未找到 安装命令: $(oput blue "${osDCMD} install -y ${CMD}")"
                    ;;
            esac
            echo; return 1
        fi
    done
}

# 禁用 selinux
function disable_selinux()
{
    # 禁用 selinux -- 永久禁用的时候需要重启 这东东经常莫名其妙的导致编译问题或服务启动问题
    if [ -s /etc/selinux/config ]; then
        if grep -q 'SELINUX=enforcing' /etc/selinux/config; then
            sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config
        fi
    fi
    setenforce 0 &>/dev/null
}

function create_groupAndUser() {
    local sysGroupName sysUserName groupadd_cmd useradd_cmd
    sysGroupName="mysql"
    sysUserName="mysql"
    groupadd_cmd="groupadd ${sysGroupName}"
    useradd_cmd="useradd -r -g ${sysGroupName} -s /bin/false ${sysUserName}"
    echo -e "组和用户: ${sysGroupName} \c"
    # 判断组是否存在
    if ! grep -Eq "^${sysGroupName}" /etc/group; then
        if eval "${groupadd_cmd}" &>>"${sLogFile}"; then
            oput green "创建系统组成功"
        else
            oput red "创建系统组失败"
            return 1
        fi
    else
        oput yellow "系统组已存在 无需再次创建"
    fi
    echo -e "组和用户: ${sysUserName} \c"
    # 判断用户是否存在
    if ! grep -Eq "^${sysUserName}" /etc/passwd; then
        if eval "${useradd_cmd}" &>>"${sLogFile}"; then
            oput green "创建系统用户成功"
        else
            oput red "创建系统用户失败"
            return 1
        fi
    else
        oput yellow "系统用户已存在 无需再次创建"
    fi
}

# 根据不同服务管理方式创建不同的服务控制文件
function create_sysSrvFile() {
    echo -e "创建文件: ${sysSrvFile} \c"
    if [ "${initIs}" != "systemd" ]; then
        oput red "失败 当前系统不支持 systemd"
        return 1
    fi

    # 如果服务控制文件存在 则备份
    if [ -f "${sysSrvFile}" ]; then
        cp "${sysSrvFile}" "${sysSrvFile}-$(date '+%Y%m%d%H%M%S')"
    fi

    cat >"${sysSrvFile}" <<EOF
[Unit]
Description=MySQL Server
Documentation=man:mysqld(8)
Documentation=https://dev.mysql.com/doc/refman/en/using-systemd.html
After=network.target syslog.target
[Install]
WantedBy=multi-user.target
[Service]
User=mysql
Group=mysql
Type=forking
# Disable service start and stop timeout logic of systemd for mysqld service.
TimeoutSec=0
# Execute pre and post scripts as root
PermissionsStartOnly=true
# Start main service
ExecStart=${appSrvFile} start
# Sets open_files_limit
LimitNOFILE = 65535
Restart=on-failure
RestartPreventExitStatus=1
# Set environment variable MYSQLD_PARENT_PID. This is required for restart.
Environment=MYSQLD_PARENT_PID=1
PrivateTmp=false
EOF

    if [ -f "${sysSrvFile}" ]; then
        oput green "成功"
    else
        oput red "失败"
        return 1
    fi
    systemctl daemon-reload
}

# 设置软件运行参数
function set_appPara() {
    local dbIBPS
    dbIBPS="$(awk "BEGIN {print ${memTotal}*1024*${dbIBPSizeUseMemPercent}}")"
    echo -e "创建文件: ${appEtcFile} \c"
    case "${tarFileMajorVer}.${tarFileMinorVer}" in
        "5.5" )
            # 安装 mysql 5.5.x 的代码段 默认不启用 log_bin
            cat >"${appEtcFile}" <<EOF
[client]
port=${dbPort}
default-character-set=${dbCharacter}
socket=${sockFileClient}

[mysql]
default-character-set=${dbCharacter}
socket=${sockFileMysql}

[mysqld]
port=${dbPort}
character-set-server=${dbCharacter}
basedir=${appInstallDir}
datadir=${appDataDir}
pid-file=${appPidFile}
user=mysql
slow_query_log=${slowQueryLog}
lower_case_table_names=1
# skip-name-resolve
socket=${sockFileServer}
default-storage-engine=${dbEngine}
max_connections=${dbMaxConnect}
max_connect_errors=1000
max_allowed_packet=${dbMaxAllowedPacket}M
# innodb_log_files_in_group=3
innodb_log_file_size=256M
innodb_log_buffer_size=512M
innodb_buffer_pool_size=${dbIBPS%.*}M
# innodb_additional_mem_pool_size 和 innodb_use_sys_malloc 在 MySQL 5.7.4 中移除
innodb_additional_mem_pool_size=256M
innodb_flush_method=${innodbFlushMethod}
innodb_lock_wait_timeout=1800
innodb_flush_log_at_trx_commit=0
innodb_autoextend_increment=64
# innodb_lru_scan_depth=256
# innodb_io_capacity=
# innodb_io_capacity_max=
# 内存缓冲池个数 即控制并发读写 innodb_buffer_pool 的个数
# innodb_buffer_pool_instances=CPU核数
# innodb_read_io_threads=64
# innodb_write_io_threads=64
# innodb_open_files=300
# innodb_file_per_table=1
# innodb_file_format=Barracuda
# innodb_page_size=${innodbPageSize}K
# innodb_thread_concurrency=并发线程数(官方推荐CPU核数的2倍)
# sync_binlog=0
group_concat_max_len=1024000
transaction-isolation=${dbTransactionIsolation}
default-time-zone='${dbDefaultTimeZone}'
# query_cache_limit 在 MySQL 8.0.3 移除
query_cache_limit=2M
# query_cache_size 在 MySQL 8.0.3 移除
query_cache_size=64M
tmp_table_size=512M
thread_cache_size=32
# key_buffer_size 仅对 myisam 有效
key_buffer_size=256M
read_buffer_size=1M
read_rnd_buffer_size=128M
sort_buffer_size=1M
sql_mode="NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES"
# 安全加固
skip_symbolic_links=0
local-infile=0
# connect_timeout=60

# skip_ssl

[mysqld_safe]
log-error=${appErrLogFile}

[mysql.server]
basedir=${appInstallDir}
EOF
            ;;
        "5.6" )
            # 安装 mysql 5.6.x 的代码段 默认不启用 log_bin
            cat > "${appEtcFile}" <<EOF
[client]
port=${dbPort}
default-character-set=${dbCharacter}
socket=${sockFileClient}

[mysql]
default-character-set=${dbCharacter}
socket=${sockFileMysql}

[mysqld]
port=${dbPort}
character-set-server=${dbCharacter}
basedir=${appInstallDir}
datadir=${appDataDir}
pid-file=${appPidFile}
user=mysql
slow_query_log=${slowQueryLog}
lower_case_table_names=1
# skip-name-resolve
socket=${sockFileServer}
default-storage-engine=${dbEngine}
max_connections=${dbMaxConnect}
max_connect_errors=1000
max_allowed_packet=${dbMaxAllowedPacket}M
# innodb_log_files_in_group=3
innodb_log_file_size=256M
innodb_log_buffer_size=512M
innodb_buffer_pool_size=${dbIBPS%.*}M
# innodb_additional_mem_pool_size 和 innodb_use_sys_malloc 在 MySQL 5.7.4 中移除
innodb_additional_mem_pool_size=256M
innodb_flush_method=${innodbFlushMethod}
innodb_lock_wait_timeout=1800
innodb_flush_log_at_trx_commit=1
innodb_autoextend_increment=64
# innodb_lru_scan_depth=256
# innodb_io_capacity=
# innodb_io_capacity_max=
# innodb_buffer_pool_instances=CPU核数
# innodb_read_io_threads=64
# innodb_write_io_threads=64
# innodb_open_files=300
innodb_file_per_table=1
# innodb_file_format=Barracuda
innodb_page_size=${innodbPageSize}K
# innodb_thread_concurrency=并发线程数(官方推荐CPU核数的2倍)
# sync_binlog=0
group_concat_max_len=1024000
transaction-isolation=${dbTransactionIsolation}
default-time-zone='${dbDefaultTimeZone}'
# query_cache_limit 在 MySQL 8.0.3 移除
query_cache_limit=2M
# query_cache_size 在 MySQL 8.0.3 移除
query_cache_size=64M
tmp_table_size=1024M
max_heap_table_size=1024M
join_buffer_size=1024M
thread_cache_size=32
# key_buffer_size 仅对 myisam 有效
key_buffer_size=256M
read_buffer_size=1M
read_rnd_buffer_size=128M
sort_buffer_size=1M
sql_mode="NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES"
# 安全加固
skip_symbolic_links=yes
local-infile=0
# connect_timeout=60

# skip_ssl

[mysqld_safe]
log-error=${appErrLogFile}

[mysql.server]
basedir=${appInstallDir}
EOF
            ;;
        "5.7" )
            # 安装 mysql 5.7.x 的代码段 默认不启用 log_bin
            cat >"${appEtcFile}" <<EOF
[client]
port=${dbPort}
default-character-set=${dbCharacter}
socket=${sockFileClient}

[mysql]
default-character-set=${dbCharacter}
socket=${sockFileMysql}

[mysqld]
port=${dbPort}
character-set-server=${dbCharacter}
init_connect='SET NAMES ${dbCharacter}'
skip-character-set-client-handshake=true
basedir=${appInstallDir}
datadir=${appDataDir}
pid-file=${appPidFile}
user=mysql
slow_query_log=${slowQueryLog}
# slow-query-log-file=默认为主机名-slow.log
# long_query_time=10
# log_queries_not_using_indexes=OFF
# log_output=file,table
# expire_logs_days=60
lower_case_table_names=1
explicit_defaults_for_timestamp=true
# skip-grant-tables
# skip-name-resolve
socket=${sockFileServer}
default-storage-engine=${dbEngine}
max_connections=${dbMaxConnect}
max_connect_errors=1000
max_allowed_packet=${dbMaxAllowedPacket}M
# innodb_log_files_in_group=3
innodb_log_file_size=256M
innodb_log_buffer_size=512M
innodb_buffer_pool_size=${dbIBPS%.*}M
innodb_flush_method=${innodbFlushMethod}
innodb_lock_wait_timeout=1800
innodb_flush_log_at_trx_commit=1
innodb_autoextend_increment=64
# innodb_lru_scan_depth=256
# innodb_io_capacity=
# innodb_io_capacity_max=
# innodb_buffer_pool_instances=CPU核数
# innodb_read_io_threads=64
# innodb_write_io_threads=64
# innodb_open_files=300
innodb_file_per_table=1
# innodb_file_format=Barracuda
innodb_page_size=${innodbPageSize}K
# innodb_thread_concurrency=并发线程数(官方推荐CPU核数的2倍)
# sync_binlog=0
group_concat_max_len=1024000
transaction-isolation=${dbTransactionIsolation}
default-time-zone='${dbDefaultTimeZone}'
# query_cache_limit 在 MySQL 8.0.3 移除
query_cache_limit=2M
# query_cache_size 在 MySQL 8.0.3 移除
query_cache_size=128M
tmp_table_size=1024M
max_heap_table_size=1024M
join_buffer_size=1024M
thread_cache_size=32
# key_buffer_size 仅对 myisam 有效
key_buffer_size=256M
read_buffer_size=1M
read_rnd_buffer_size=128M
sort_buffer_size=1M
sql_mode="NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES"
log_timestamps=system
# 安全加固
skip_symbolic_links=yes
local-infile=0
max_connect_errors=1000
# connect_timeout=60

# skip_ssl

[mysqld_safe]
log-error=${appErrLogFile}

[mysql.server]
basedir=${appInstallDir}
EOF
            ;;
        "8.0" )
            # 安装 mysql 8.0.x 的代码段 默认已启用 log_bin
            cat >"${appEtcFile}" <<EOF
[client]
port=${dbPort}
default-character-set=${dbCharacter}
socket=${sockFileClient}

[mysql]
default-character-set=${dbCharacter}
socket=${sockFileMysql}

[mysqld]
port=${dbPort}
character-set-server=${dbCharacter}
init_connect='SET NAMES ${dbCharacter}'
skip-character-set-client-handshake=true
basedir=${appInstallDir}
datadir=${appDataDir}
pid-file=${appPidFile}
user=mysql
slow_query_log=${slowQueryLog}
# slow-query-log-file=默认为主机名-slow.log
# long_query_time=10
# log_queries_not_using_indexes=OFF
# log_output=file,table
# skip-name-resolve=1
lower_case_table_names=1
# expire_logs_days=60
socket=${sockFileServer}
default-storage-engine=${dbEngine}
# 字典对象缓存实时更新 此参数对性能有一定影响
# information_schema_stats_expiry=0
max_connections=${dbMaxConnect}
max_connect_errors=1000
tmp_table_size=1024M
max_heap_table_size=1024M
join_buffer_size=1024M
thread_cache_size=32
# key_buffer_size 仅对 myisam 有效
key_buffer_size=256M
read_buffer_size=1M
read_rnd_buffer_size=128M
# default_authentication_plugin=mysql_native_password
default_authentication_plugin=${dbDefaultAuthPlugin}
sql_mode="NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES"
max_allowed_packet=${dbMaxAllowedPacket}M
# 从MySQL 8.0.30开始，innodb_redo_log_capacity 系统变量控制 Redo 日志文件占用的磁盘空间。
# 取代了innodb_log_files_in_group 变量和 innodb_log_file_size 变量，并已经被弃用。
# 如下表示 将Redo日志容量设置为8GB
# innodb_redo_log_capacity = 8589934592
# innodb_log_files_in_group=3
innodb_log_file_size=256M
innodb_log_buffer_size=512M
innodb_buffer_pool_size=${dbIBPS%.*}M
innodb_flush_method=${innodbFlushMethod}
innodb_lock_wait_timeout=1800
innodb_flush_log_at_trx_commit=1
innodb_autoextend_increment=64
# innodb_lru_scan_depth=256
# innodb_io_capacity=
# innodb_io_capacity_max=
# innodb_buffer_pool_instances=CPU核数
# innodb_read_io_threads=64
# innodb_write_io_threads=64
# innodb_open_files=300
innodb_file_per_table=1
# innodb_file_format=Barracuda
innodb_page_size=${innodbPageSize}K
# innodb_thread_concurrency=并发线程数(官方推荐CPU核数的2倍)
group_concat_max_len=1024000
transaction-isolation=${dbTransactionIsolation}
default-time-zone='${dbDefaultTimeZone}'

# mysql 主从相关设置 - 主库
server-id=${hostAddr##*.}
log_bin=${hostName}-mysql-bin
# 指定 mysql 的 binlog 日志记录哪个库 缺点: 过滤操作带来的负载都在 master 上 无法做基于时间点的复制(利用 binlog)
# binlog-do-db=oadb
# binlog_format 从 MySQL 8.0.34 开始被废弃
binlog_format=row
# max_binlog_size=256M
# 归档日志过期时间 单位秒 86400秒=1天 604800秒=7天 2592000秒=30天
binlog_expire_logs_seconds=${dbBinLogExpireLimit}
## 指定 slave 要复制哪个库 参数是在 slave 上配置 如果有多个库 需要多行列举
# replicate-do-db=oadb
replicate_wild_do_table=oadb.%
# binlog-ignore-db=information_schema
# binlog-ignore-db=mysql
# binlog-ignore-db=performance_schema
# binlog-ignore-db=sys
# replicate_ignore_db=information_schema
# replicate_ignore_db=mysql
# replicate_ignore_db=performance_schema
# replicate_ignore_db=sys
# 主库时auto-increment-offset=1 从库时auto-increment-offset=2
auto-increment-offset=1
auto-increment-increment=2
## 8.0.26 之前的版本使用 log_slave_updates 为了兼容类似 8.0.11 的老版本在安装时能启动服务 所以注释了 如果需要请手动开启
# log_replica_updates=1
sync_binlog=1
## 8.0.26 之前的版本使用 slave_parallel_type | 8.0.29 废弃
# replica_parallel_type=LOGICAL_CLOCK
# replica_parallel_workers=4
# slave-skip-errors=all
# read-only=1
# gtid 相关设置
gtid_mode=ON
enforce_gtid_consistency=ON
# 为了兼容类似 8.0.11 的老版本在安装时能启动服务 所以注释了 如果需要请手动开启
# skip_replica_start=ON
relay-log=${hostName}-relay-bin
# max_relay_log_size=
# relay_log_space_limit=
# relay_log_purge=ON
# relay_log_recovery=ON

# 安全加固
local-infile=0
log_timestamps=system
max_connect_errors=1000
# connect_timeout=60

# skip_ssl

# 国密支持 需满足前提条件 否则无需开启
# require_secure_transport=ON
# tls_ciphersuites=TLS_SM4_GCM_SM3:TLS_SM4_CCM_SM3
# tls_version=TLSv1.3

[mysqld_safe]
log-error=${appErrLogFile}

[mysql.server]
basedir=${appInstallDir}
EOF
            ;;
        "8.4" )
            # 安装 mysql 8.0.x 的代码段 默认已启用 log_bin
            cat >"${appEtcFile}" <<EOF
[client]
port=${dbPort}
default-character-set=${dbCharacter}
socket=${sockFileClient}

[mysql]
default-character-set=${dbCharacter}
socket=${sockFileMysql}

[mysqld]
port=${dbPort}
character-set-server=${dbCharacter}
init_connect='SET NAMES ${dbCharacter}'
basedir=${appInstallDir}
datadir=${appDataDir}
pid-file=${appPidFile}
user=mysql
slow_query_log=${slowQueryLog}
# slow-query-log-file=默认为主机名-slow.log
# long_query_time=10
# log_queries_not_using_indexes=OFF
# log_output=file,table
# skip-name-resolve=1
lower_case_table_names=1
# expire_logs_days=60
socket=${sockFileServer}
default-storage-engine=${dbEngine}
# 字典对象缓存实时更新 此参数对性能有一定影响
# information_schema_stats_expiry=0
max_connections=${dbMaxConnect}
max_connect_errors=1000
tmp_table_size=1024M
max_heap_table_size=1024M
join_buffer_size=1024M
thread_cache_size=32
# key_buffer_size 仅对 myisam 有效
key_buffer_size=256M
read_buffer_size=1M
read_rnd_buffer_size=128M
# default_authentication_plugin=${dbDefaultAuthPlugin}
authentication_policy=${dbDefaultAuthPlugin}
sql_mode="NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES"
max_allowed_packet=${dbMaxAllowedPacket}M
# 从MySQL 8.0.30开始，innodb_redo_log_capacity 系统变量控制 Redo 日志文件占用的磁盘空间。
# 取代了innodb_log_files_in_group 变量和 innodb_log_file_size 变量，并已经被弃用。
# 如下表示 将Redo日志容量设置为8GB
innodb_redo_log_capacity=2G
# innodb_log_files_in_group=3
innodb_log_buffer_size=512M
innodb_buffer_pool_size=${dbIBPS%.*}M
innodb_flush_method=${innodbFlushMethod}
innodb_lock_wait_timeout=1800
innodb_flush_log_at_trx_commit=1
innodb_autoextend_increment=64
# innodb_lru_scan_depth=256
# innodb_io_capacity=
# innodb_io_capacity_max=
# innodb_buffer_pool_instances=CPU核数
# innodb_read_io_threads=64
# innodb_write_io_threads=64
# innodb_open_files=300
innodb_file_per_table=1
# innodb_file_format=Barracuda
innodb_page_size=${innodbPageSize}K
# innodb_thread_concurrency=并发线程数(官方推荐CPU核数的2倍)
group_concat_max_len=1024000
transaction-isolation=${dbTransactionIsolation}
default-time-zone='${dbDefaultTimeZone}'

# mysql 主从相关设置 - 主库
server-id=${hostAddr##*.}
log_bin=${hostName}-mysql-bin
# 指定 mysql 的 binlog 日志记录哪个库 缺点: 过滤操作带来的负载都在 master 上 无法做基于时间点的复制(利用 binlog)
# binlog-do-db=oadb
# binlog_format 从 MySQL 8.0.34 开始被废弃
binlog_format=row
# max_binlog_size=256M
# 归档日志过期时间 单位秒 86400秒=1天 604800秒=7天 2592000秒=30天
binlog_expire_logs_seconds=${dbBinLogExpireLimit}
## 指定 slave 要复制哪个库 参数是在 slave 上配置 如果有多个库 需要多行列举
# replicate-do-db=oadb
replicate_wild_do_table=oadb.%
auto-increment-offset=1
auto-increment-increment=2
## 8.0.26 之前的版本使用 log_slave_updates 为了兼容类似 8.0.11 的老版本在安装时能启动服务 所以注释了 如果需要请手动开启
# log_replica_updates=1
sync_binlog=1
## 8.0.26 之前的版本使用 slave_parallel_type | 8.0.29 废弃
# replica_parallel_type=LOGICAL_CLOCK
# replica_parallel_workers=4
# slave-skip-errors=all
# read-only=1
# gtid 相关设置
gtid_mode=ON
enforce_gtid_consistency=ON
# 为了兼容类似 8.0.11 的老版本在安装时能启动服务 所以注释了 如果需要请手动开启
# skip_replica_start=ON
relay-log=${hostName}-relay-bin
# max_relay_log_size=
# relay_log_space_limit=
# relay_log_purge=ON
# relay_log_recovery=ON

# 安全加固
local-infile=0
log_timestamps=system
max_connect_errors=1000
# connect_timeout=60

# skip_ssl

# 国密支持 需满足前提条件 否则无需开启
# require_secure_transport=ON
# tls_ciphersuites=TLS_SM4_GCM_SM3:TLS_SM4_CCM_SM3
# tls_version=TLSv1.3

[mysqld_safe]
log-error=${appErrLogFile}

[mysql.server]
basedir=${appInstallDir}
EOF
            ;;
        * )
            oput red "脚本不支持创建当前版本的服务控制文件"
            return 1
            ;;
    esac

    if [ -f "${appEtcFile}" ]; then
        oput green "成功"
        chown mysql:mysql "${appEtcFile}"
        chmod 644 "${appEtcFile}"
    else
        oput red "失败"
        return 1
    fi

    local initDBStrs
    case "${tarFileMajorVer}.${tarFileMinorVer}" in
        "5.5" )
            # 安装 mysql 5.5.x 的代码段
            initDBStrs="${appInstallDir}/scripts/mysql_install_db \
                --user=mysql --defaults-file=${appEtcFile} \
                --basedir=${appInstallDir} \
                --datadir=${appDataDir} \
                --skip-name-resolve \
                --user=mysql"
            ;;
        "5.6" )
            # 安装 mysql 5.6.x 的代码段
            initDBStrs="${appInstallDir}/scripts/mysql_install_db \
                --user=mysql --defaults-file=${appEtcFile} \
                --basedir=${appInstallDir} \
                --datadir=${appDataDir} \
                --skip-name-resolve \
                --user=mysql"
            ;;
        "5.7" | "8.0" | "8.4" )
            initDBStrs="${appSbinFile} \
                --defaults-file=${appEtcFile} \
                --initialize-insecure \
                --user=mysql"
            ;;
        * )
            echo "只支持 MySQL 5.5/5.6/5.7/8.0/8.4 系列的版本"
            return 1
            ;;
    esac
    echo -e "初 始 化: 数据库实例 \c"
    if eval "${initDBStrs}" &>>"${sLogFile}"; then oput green "成功"; else oput red "失败" && return 1; fi

    # 某些环境变量异常的情况下 可取消以下两行注释来使用系统默认的openssl
    # openssl_cnf="$(/usr/bin/openssl version -a | grep '^OPENSSLDIR' | awk '{print $2}' | sed 's/\"//g')/openssl.cnf"
    # export OPENSSL_CONF="${openssl_cnf}"

    # 脚本设定 开启SSL功能 只支持 5.7/8.0
    local initSSLStrs
    case "${tarFileMajorVer}.${tarFileMinorVer}" in
        "5.5" | "5.6" )
            # 不做 ssl 处理
            ;;
        "5.7" | "8.0" | "8.4" )
            initSSLStrs="${appBinDir}/mysql_ssl_rsa_setup --datadir=${appDataDir}"
            echo -e "生成密钥: \c"
            if eval "${initSSLStrs}" &>>"${sLogFile}"; then oput green "成功"; else oput red "失败" && return 1; fi
            ;;
    esac

    # sysvinit 和 systemd 都配置此文件 ${appInstallDir}/support-files/mysql.server
    # https://dev.mysql.com/doc/refman/8.0/en/mysql-server.html

    echo; echo "修改文件: ${appSrvFile}"
    sed -i "s%^basedir=$%basedir=${appInstallDir}%g" "${appSrvFile}"
    echo -e "修改参数: basedir=${appInstallDir} \c"
    oput green "完成"

    sed -i "s%^datadir=$%datadir=${appDataDir}%g" "${appSrvFile}"
    echo -e "修改参数: datadir=${appDataDir} \c"
    oput green "完成"

    sed -i "s%mysqld_safe --datadir=%mysqld_safe --defaults-file=\"\$basedir/my.cnf\" --datadir=%" "${appSrvFile}"
    echo -e "修改参数: defaults-file=${appEtcFile} \c"
    oput green "完成"
}

# 设置服务开机自启
function set_auto_start() {
    if [ "${initIs}" != "systemd" ] ; then
        local autoSrvCMD
        autoSrvCMD="${installRootDir}/${appName}/support-files/mysql.server start"
    fi

    case "${setAutoStartAction}" in
        "w" )
            echo -e "开机自启: \c"
            case "${enableAutostart}" in
                "1" )
                    if [ "${initIs}" = "systemd" ]; then
                        if systemctl enable "${sysSrvFile##*/}" &>>"${sLogFile}"; then
                            oput green "配置成功"
                        else
                            oput red "配置失败" && return 1
                        fi
                    else
                        oput red "当前系统不支持 systemd"
                        echo -e "尝试添加: ${autoSrvCMD} \c"
                        if [ -f /etc/rc.local ]; then
                            if grep -q "^${autoSrvCMD}" /etc/rc.local; then
                                oput yellow "已有相同内容 无需再次写入开机自启动命令到 /etc/rc.local"
                            else
                                if grep -q '^exit 0' /etc/rc.local; then
                                    sed -i '/exit 0/d' /etc/rc.local
                                    (echo; echo "${autoSrvCMD}"; echo "exit 0") >>/etc/rc.local
                                    oput green "已写入开机自启动命令到 /etc/rc.local"
                                else
                                    echo "${autoSrvCMD}" >>/etc/rc.local
                                    oput green "已写入开机自启动命令到 /etc/rc.local"
                                fi
                            fi
                        else
                            oput red "文件不存在 /etc/rc.local"
                            oput blue "请手动添加 ${autoSrvCMD}"
                            return 1
                        fi
                    fi
                    ;;
                * )
                    oput yellow "无需配置 脚本未启用此参数"
                    ;;
            esac
            ;;
        "d" )
            if [ "${initIs}" = "systemd" ]; then
                local willDelFiles
                willDelFiles=(
                    "/usr/lib/systemd/system/mysqld.service"
                    "/lib/systemd/system/mysqld.service"
                    "/etc/systemd/system/mysqld.service"
                    )
                for willDelFile in "${willDelFiles[@]}"; do
                    # RedHat/Suse: /usr/lib/systemd/system/mysqld.service
                    # Ubuntu/Deepin: /lib/systemd/system/mysqld.service
                    if [ -f "${willDelFile}" ]; then
                        echo -e "自启清理: ${willDelFile} \c"
                        if systemctl disable "${willDelFile##*/}" &>>"${sLogFile}"; then
                            rm -f "${willDelFile}" &>>"${sLogFile}"
                            oput green "清理成功 且已删除自启文件"
                        else
                            oput red "清理失败" && return 1
                        fi
                    else
                        echo -e "自启清理: ${willDelFile} \c"
                        oput yellow "未找到 无需任何操作"
                    fi
                done
            else
                echo -e "自启清理: ${autoSrvCMD} \c"
                if [ -f /etc/rc.local ]; then
                    if grep -q "^${autoSrvCMD}" /etc/rc.local; then
                        sed -i '/.*support-files.*mysql.server start/d' /etc/rc.local
                        oput green "清理完成"
                    else
                        oput yellow "未找到 无需任何操作"
                    fi
                else
                    oput red "清理失败 文件不存在 /etc/rc.local"
                    return 1
                fi
            fi
            ;;
    esac
}

function set_env_var() {
    local envStrLD envStrPATH
    envStrLD="export LD_LIBRARY_PATH=${appLibDir}:\$LD_LIBRARY_PATH"
    envStrPATH="export PATH=${appBinDir}:\$PATH"
    case "${setEnvAction}" in
        "w" )
            echo -e "环境变量: ${envStrLD} \c"
            case "${enableWriteEnvLD}" in
                "1" )
                    if grep -Eq "^export LD_LIBRARY_PATH=${appLibDir}.*LD_LIBRARY_PATH" "${sysEnvFile}"; then
                        oput yellow "已有相同内容 无需再次写入"
                    else
                        echo "${envStrLD}" >>"${sysEnvFile}"
                        oput green "已写入"
                    fi
                    ;;
                * )
                    oput yellow "无需写入 脚本未启用此参数"
                    ;;
            esac
            echo -e "环境变量: ${envStrPATH} \c"
            case "${enableWriteEnvPATH}" in
                "1" )
                    if grep -Eq "^export PATH=${appBinDir}.*PATH" "${sysEnvFile}"; then
                        oput yellow "已有相同内容 无需再次写入"
                    else
                        echo "${envStrPATH}" >>"${sysEnvFile}"
                        oput green "已写入"
                    fi
                    ;;
                * )
                    oput yellow "无需写入 脚本未启用此参数"
                    ;;
            esac
            ;;
        "d" )
            echo -e "清理变量: $(oput blue "export LD_LIBRARY_PATH=${appLibDir}:\$LD_LIBRARY_PATH") \c"
            if grep -q "^export LD_LIBRARY_PATH=${appLibDir}:\$LD_LIBRARY_PATH" "${sysEnvFile}"; then
                sed -i "/^export.*${appName}\/lib:\$LD_LIBRARY_PATH/d" "${sysEnvFile}"
                oput green "完成"
            else
                oput yellow "无需清理"
            fi
            echo -e "清理变量: $(oput blue "export PATH=${appBinDir}:\$PATH") \c"
            if grep -q "^export PATH=${appBinDir}:\$PATH" "${sysEnvFile}"; then
                sed -i "/^export.*${appName}\/bin:\$PATH/d" "${sysEnvFile}"
                oput green "完成"
            else
                oput yellow "无需清理"
            fi
            ;;
    esac
}

# 防火墙设置
function set_firewall() {
    case "${osFamily}" in
        "redhat" )
            case "${firewallMethod}" in
                "add" )
                    if systemctl status firewalld &>/dev/null; then
                        if firewall-cmd --zone=public --add-port=${dbPort}/tcp --permanent &>>"${sLogFile}"; then
                            echo "防 火 墙: 开放端口 ${dbPort} $(oput green "成功")"
                            firewall-cmd --reload &>>"${sLogFile}"
                        else
                            echo "防 火 墙: 开放端口 ${dbPort} $(oput red "失败")"
                        fi
                    else
                        echo "防 火 墙: $(oput blue "未启用 无需开放端口")"
                    fi
                    ;;
                "dis" )
                    if systemctl status firewalld &>/dev/null; then
                        if systemctl stop firewalld &>>"${sLogFile}"; then
                            echo "防 火 墙: 关闭服务 $(oput green "成功")"
                            if systemctl disable firewalld &>>"${sLogFile}"; then
                                echo "防 火 墙: 禁止开机自启 $(oput green "成功")"
                            else
                                echo "防 火 墙: 禁止开机自启 $(oput red "失败")"
                            fi
                        else
                            echo "防 火 墙: 关闭服务 $(oput red "失败")"
                        fi
                    else
                        echo "防 火 墙: $(oput blue "未启用 无需再次禁用")"
                    fi
                    ;;
            esac
            ;;
        "debian" )
            case "${firewallMethod}" in
                "add" )
                    ufw allow ${dbPort}/tcp &>>"${sLogFile}"
                    ufw reload &>>"${sLogFile}"
                    ;;
                "dis" )
                    ufw disable &>>"${sLogFile}"
                    ;;
            esac
            ;;
        "suse" )
            osReleaseFile="/etc/os-release"
            osRelease="$(awk -F '[="]+' '/^PRETTY_NAME=/{print $2}' "${osReleaseFile}")"
            # osVersion="$(awk -F '[="]+' '/^VERSION_ID=/{print $2}' "${osReleaseFile}")"
            osVerMajor="$(awk -F '[="]+' '/^VERSION_ID=/{print $2}' "${osReleaseFile}" | awk -F '.' '{print $1}')"
            # osVerMinor="$(awk -F '[="]+' '/^VERSION_ID=/{print $2}' "${osReleaseFile}" | awk -F '.' '{print $2}')"

            # 一般12使用SuSE防火墙 15使用firewalld防火墙
            case "${osVerMajor}" in
                "12" )
                    case "${firewallMethod}" in
                        "add" )
                            if [ -f /etc/sysconfig/SuSEfirewall2 ]; then
                                # /etc/sysconfig/SuSEfirewall2 修改 FW_SERVICES_EXT_TCP="1433"
                                sed -i "s/^FW_SERVICES_EXT_TCP=\"/&${dbPort} /" /etc/sysconfig/SuSEfirewall2
                            fi
                            SuSEfirewall2 open INT TCP ${dbPort} &>/dev/null
                            ;;
                        "dis" )
                            if SuSEfirewall2 status &>/dev/null; then
                                SuSEfirewall2 open INT TCP ${dbPort} &>/dev/null
                                SuSEfirewall2 stop &>/dev/null
                                /sbin/SuSEfirewall2 off &>/dev/null
                                # SuSEfirewall2 start &>/dev/null
                            fi
                            ;;
                    esac
                    ;;
                "15" )
                    case "${firewallMethod}" in
                        "add" )
                            if systemctl status firewalld &>/dev/null; then
                                firewall-cmd --zone=public --add-port=${dbPort}/tcp --permanent &>>"${sLogFile}"
                                firewall-cmd --reload &>>"${sLogFile}"
                            fi
                            ;;
                        "dis" )
                            if systemctl status firewalld &>/dev/null; then
                                systemctl stop firewalld &>>"${sLogFile}"
                                systemctl disable firewalld &>>"${sLogFile}"
                            fi
                            ;;
                    esac
                    ;;
                * )
                    oput yellow "暂不支持 suse 12/15 以外的 os 的防火墙配置"
                    return 1
                    ;;
            esac
            ;;
        * )
            oput yellow "暂不支持 redhat/debian/suse 以外的 os 的防火墙配置"
            return 1
            ;;
    esac
}

function get_systemInfo() {
    # 主机
    hostName="$(hostname)"
    # CPU型号
    cpuModel="$(grep name /proc/cpuinfo | uniq | awk -F ':' '{print $(NF)}' | sed 's/^[ \t]*//g')"
    if [ "${cpuModel}" = "" ]; then cpuModel="unrecognized"; fi
    # CPU核数
    cpuLogic="$(grep -c ^processor /proc/cpuinfo 2>/dev/null)"
    # 物理内存 - 总量(低于1G的内存依旧进位1)
    memTotal="$(grep 'MemTotal' /proc/meminfo | awk '{print int($2/1024/1024+1)}')"

    # 物理磁盘
    if command -v lsblk &>/dev/null; then
        # 磁盘总量(GB)
        diskTotalSize="$(lsblk -b 2>>"${sLogFile}" | grep disk | awk '{total+=$4/1024/1024/1024}END{print total}')"
    elif command -v parted &>/dev/null; then
        # 磁盘总量(GB)
        diskTotalSize="$(parted -l | grep 'Disk /dev/' | grep -v 'mapper\|dm' | awk '{total+=int($3+0.5)}END{print total}')"
    else
        # 系统未安装 parted/lsblk 时 设定输出为 0
        diskTotalSize="0"
    fi

    # 操作系统 - 只做简单识别 懒得去把市面上的系统全都调试一遍 ^_^
    if [ -f "/etc/os-release" ]; then
        osRelease="$(awk -F '[="]+' '/^PRETTY_NAME=/{print $2}' /etc/os-release)"
    elif [ -f "/etc/redhat-release" ]; then
        osRelease="$(cat /etc/redhat-release)"
    else
        osRelease="unrecognized operating system"
    fi

    # 内核版本
    osKernelFullVer="$(uname -r | cut -d '-' -f 1)"
    osKernelMajorVer="$(uname -r | cut -d '-' -f 1 | awk -F . '{print $1}')"
    osKernelMinorVer="$(uname -r | cut -d '-' -f 1 | awk -F . '{print $2}')"

    # GNU C Library 版本
    osGlibcFullVer="$(getconf GNU_LIBC_VERSION | head -1 | awk '{print $2}')"
    # GNU C Library 主版本号
    osGlibcMajorVer="$(getconf GNU_LIBC_VERSION | head -1 | awk '{print $2}' | awk -F \. '{print $1}')"
    # GNU C Library 次版本号
    osGlibcMinorVer="$(getconf GNU_LIBC_VERSION | head -1 | awk '{print $2}' | awk -F \. '{print $2}')"
}

function get_installedVersion() {
    if [ -f "${appSbinFile}" ]; then
        installedVer="$("${appSbinFile}" --version | grep -Ewo '[0-9].[0-9].[0-9]+' 2>/dev/null)"
        # 主版本号
        installedMajorVer="$(echo "${installedVer}" | cut -d '.' -f 1)"
        # 次版本号
        installedMinorVer="$(echo "${installedVer}" | cut -d '.' -f 2)"
        # 构建号
        installedBuildVer="$(echo "${installedVer}" | cut -d '.' -f 3)"
    else
        installedVer=""
        installedMajorVer=""
        installedMinorVer=""
        installedBuildVer=""
    fi
}

# 进程和端口检查
function check_pidAndPort() {
    # 进程检查 0 = 已启动 | 1 = 未启动
    # appMainPid 为主父进程PID
    # appMainPid="$(ps -ef | grep -E "${appSbinFile}" | awk "\$3==1 {print \$2}")"
    appPidNumber="$(pgrep -l mysqld | grep -E 'mysqld$' | awk '{print $1}')"
    if [ "${appPidNumber}" != "" ]; then appRunState="0"; else appRunState="1"; fi

    # 端口检测 0=已占用 1=未占用
    local checkPortCMD
    checkPortCMD="ss -lntpd | awk '{print \$5}' | grep -Eq \":${dbPort}\$\""
    # if ss -lntpd | awk '{print $5}' | grep -Eq ":${dbPort}$"; then
    if eval "${checkPortCMD}"; then
        appPortState="0"
    else
        appPortState="1"
    fi
}

# 服务管理
function manage_srv() {
    case "${action}" in
        "start" | "stop" | "restart" | "status" ) ;;
        * ) oput red "${appName} 服务管理: 传递参数只能是 start|stop|restart|status" && return 1; ;;
    esac
    local srvManageCMD
    case "${initIs}" in
        "systemd" )
            srvManageCMD="${srvCtlCMD} ${action} ${sysSrvFile##*/}"
            ;;
        * )
            srvManageCMD="${appSrvFile} ${action}"
            ;;
    esac

    local msgChinese
    case "${action}" in
        "start" )
            msgChinese="启动服务"
            ;;
        "stop" )
            msgChinese="关闭服务"
            ;;
        "restart" )
            msgChinese="重启服务"
            ;;
        "status" )
            msgChinese="服务状态"
            ;;
    esac

    echo -e "${msgChinese}: ${srvManageCMD} \c"
    if eval "${srvManageCMD}" &>>"${sLogFile}"; then
        oput green "成功"
    else
        oput red "失败 请检查日志文件 ${sLogFile}"
        oput blue "可尝试使用以下命令启动: ${appSrvFile} start"
        return 1
    fi
}

# 查找 tarFile
function search_tarFile() {
    local findOpts fileSort findRegex
    findOpts="-maxdepth 1 -regextype \"posix-extended\" -regex"
    if sort --help | grep -Ewq '\-V,'; then
        fileSort="sed 's%^./%%' | sort -V | tail -1"
    else
        fileSort="sed 's%^./%%' | sort | tail -1"
    fi
    # findRegex=".*\.\/mysql.*linux.*.tar.(g|x)z$"
    findRegex=".*\.\/(mysql|Percona-Server).*(l|L)inux.*(${archType}|${archType}-minimal|${archType}.*glibc.*).tar.(g|x)z$"
    findCMDStr="find . ${findOpts} \"${findRegex}\" -type f | ${fileSort}"

    # echo "执行文件查找: ${findCMDStr}"
    if tarFile="$(eval "${findCMDStr}" 2>>"${sLogFile}")"; then
        if [ "${tarFile}" = "" ]; then return 1; fi
        # 提取二进制包的版本
        tarFileFullVer="$(echo "${tarFile}" | grep -Eo '[0-9].[0-9].[0-9]+')"
        # 主版本号
        tarFileMajorVer="$(echo "${tarFileFullVer}" | cut -d '.' -f 1)"
        # 次版本号
        tarFileMinorVer="$(echo "${tarFileFullVer}" | cut -d '.' -f 2)"
        # 构建版本
        tarFileBuildVer="$(echo "${tarFileFullVer}" | cut -d '.' -f 3)"

        # 获取二进制包文件的大小
        tarFileSize="$(du -sh "${tarFile}" | awk '{print $1}')"

        # 获取二进制包文件的后缀名
        case "${tarFile:0-7}" in
            ".tar.xz" )
                tarFileFormat="txz"
                ;;
            ".tar.gz" )
                tarFileFormat="tgz"
                ;;
        esac

        # 设定官方和非官方发行版标签
        if echo "${tarFile}" | grep -Eqi "^mysql"; then
            tarFileReleaseTag="mysql"
        elif echo "${tarFile}" | grep -Eqi "^Percona-Server"; then
            tarFileReleaseTag="percona"
        fi

        case "${tarFileReleaseTag}" in
            "mysql" )
                # 判断 MySQL 二进制包是否包含内核'linux'和GUN C库的'glibc'关键字 通过命名方式区分二进制包适应的平台环境
                if echo "${tarFile}" | grep -Eq 'linux[0-9]'; then
                    # 二进制包命名方式为: linux 以前老版本的命名方式采用内核相应版本
                    tarFileStyle="linux"
                    tarLinuxFullVer="$(echo "${tarFile}" | grep -Eo 'linux[0-9.]+' | grep -Eo '[0-9.]+')"
                    tarLinuxMajorVer="$(echo "${tarLinuxFullVer}" | awk -F . '{print $1}')"
                    tarLinuxMinorVer="$(echo "${tarLinuxFullVer}" | awk -F . '{print $2}')"
                elif echo "${tarFile}" | grep -Eq 'glibc[0-9]'; then
                    # 二进制包命名方式为: glibc 从 mysql 5.5.56 开始 采用 glibc 命名
                    tarFileStyle="glibc"
                    tarGlibcFullVer="$(echo "${tarFile}" | grep -Eo 'glibc[0-9.]+' | grep -Eo '[0-9.]+')"
                    tarGlibcMajorVer="$(echo "${tarGlibcFullVer}" | awk -F . '{print $1}')"
                    tarGlibcMinorVer="$(echo "${tarGlibcFullVer}" | awk -F . '{print $2}')"
                elif echo "${tarFile}" | grep -Eq 'minimal'; then
                    # 二进制包命名方式为: glibc + minimal 从 mysql 8.0.16 提供最小安装版 从8.0.21开始minimal包名会带上 glibc的版本
                    tarFileStyle="minimal"
                else
                    tarFileStyle="unkown"
                    # echo "包名规则: 不在脚本支持范围"
                    return 1
                fi
                ;;
            "percona" )
                # 判断 MySQL 二进制包是否包含内核'linux'和GUN C库的'glibc'关键字 通过命名方式区分二进制包适应的平台环境
                if echo "${tarFile}" | grep -Eqi 'minimal'; then
                    # 二进制包命名方式为: glibc + minimal 从 mysql 8.0.16 提供最小安装版 从8.0.21开始minimal包名会带上 glibc的版本
                    tarFileStyle="minimal"
                    tarGlibcFullVer="$(echo "${tarFile}" | grep -Eo 'glibc[0-9.]+' | grep -Eo '[0-9.]+')"
                    tarGlibcMajorVer="$(echo "${tarGlibcFullVer}" | awk -F . '{print $1}')"
                    tarGlibcMinorVer="$(echo "${tarGlibcFullVer}" | awk -F . '{print $2}')"
                elif echo "${tarFile}" | grep -Eq 'glibc[0-9]'; then
                    tarFileStyle="glibc"
                    tarGlibcFullVer="$(echo "${tarFile}" | grep -Eo 'glibc[0-9.]+' | grep -Eo '[0-9.]+')"
                    tarGlibcMajorVer="$(echo "${tarGlibcFullVer}" | awk -F . '{print $1}')"
                    tarGlibcMinorVer="$(echo "${tarGlibcFullVer}" | awk -F . '{print $2}')"
                else
                    tarFileStyle="unkown"
                    # echo "包名规则: 不在脚本支持范围"
                    return 1
                fi
                ;;
        esac
        # echo "文件查找结果: 已找到文件 ${tarFile} - ${tarFileSize}"
    else
        # echo "文件查找命令: 执行失败 ${findCMDStr}"
        # echo "脚本日志文件: ${sLogFile}"
        return 1
    fi
}

# 解压 tarFile
function unpack_tarFile() {
    local ucp
    # 解包后的目录相对路径 ucp=use-compress-program
    case "${tarFileFormat}" in
        "tgz" )
            ucp="gzip"
            unpackDir="${tarFile%.tar.gz}"
            ;;
        "txz" )
            ucp="xz"
            unpackDir="${tarFile%.tar.xz}"
            ;;
        * )
            unpackDir=""
            oput red "解包失败: 二进制安装包文件后缀名必须是 tar.xz 或 tar.gz"
            return 1
            ;;
    esac
    if ! command -v "${ucp}" &>/dev/null; then oput red "解包失败: ${ucp} 命令未找到" && return 1; fi

    # 如果解包目录已存在 则删除
    if [ -d "${installRootDir}/${unpackDir}" ] && [ "${unpackDir}" != "" ]; then
        echo -e "删除目录: ${installRootDir}/${unpackDir} \c"
        if rm -rf "${installRootDir:?parameter null or not set}/${unpackDir:?parameter null or not set}"; then
            oput green "成功"
        else
            oput red "失败"
            return 1
    fi
    fi
    # 解压命令 untar
    unpackCMD="tar -xf ${tarFile} -C ${installRootDir}"
    # 如果需要手动指定调用的解压程序 则按需调整后 使用下行解压命令
    # unpackCMD="tar --use-compress-program=xz -xf ${tarFile} -C ${installRootDir}"

    # 解压二进制包文件
    echo -e "正在解包: ${tarFile} \c"
    if eval "${unpackCMD}" 2>>"${sLogFile}"; then
        oput green "成功"
    else
        oput red "失败"
        return 1
    fi
}

# 版本限制
function check_versionLimit() {
    # 软件层面
    # 版本限制参考 https://www.mysql.com/support/supportedplatforms/database.html
    # 限制 MySQL 版本号必须等于 5.5/5.6/5.7/8.0

    # 受支持的官方版和Percona版
    case "${tarFileReleaseTag}" in
        "mysql" )
            case "${tarFileMajorVer}.${tarFileMinorVer}" in
                5.[5-7] | 8.[04] )
                    ;;
                * )
                    oput red "当前版本: MySQL ${tarFileFullVer} 不是 MySQL 5.5/5.6/5.7/8.0/8.4"
                    return 1
                    ;;
            esac
            ;;
        "percona" )
            case "${tarFileMajorVer}.${tarFileMinorVer}" in
                "5.7" | 8.[04] )
                    ;;
                * )
                    oput red "当前版本: Percona Server for MySQL ${tarFileFullVer} 的版本不是 MySQL 5.7/8.0/8.4"
                    return 1
                    ;;
            esac
            ;;
        * )
            oput red "脚本只支持 mysql 和 percona" && return 1
            ;;
    esac

    echo; echo -e "版本检查: \c"
    case "${tarFileMajorVer}.${tarFileMinorVer}" in
        # MySQL 5.x 系列
        "5.5" )
            # 5.5.x 限制最低版本号为 5.5.8
            if [ "${tarFileBuildVer}" -ge 8 ] ; then
                oput green "当前 ${tarFileFullVer} >= 预期 5.5.8 - 已满足条件"
            else
                oput red "当前 ${tarFileFullVer} < 预期 5.5.8 - 未满足条件"
                return 1
            fi
            ;;
        "5.6" )
            # 5.6.x 限制最低版本号为 5.6.10
            if [ "${tarFileBuildVer}" -ge 10 ] ; then
                oput green "当前 ${tarFileFullVer} >= 预期 5.6.10 - 已满足条件"
            else
                oput red "当前 ${tarFileFullVer} < 预期 5.6.10 - 未满足条件"
                return 1
            fi
            ;;
        "5.7" )
            case "${tarFileReleaseTag}" in
                "mysql" )
                    # 5.7.x 限制最低版本号为 5.7.9
                    if [ "${tarFileBuildVer}" -ge 9 ] ; then
                        oput green "当前 ${tarFileFullVer} >= 预期 5.7.9 - 已满足条件"
                    else
                        oput red "当前 ${tarFileFullVer} < 预期 5.7.9 - 未满足条件"
                        return 1
                    fi
                    ;;
                "percona" )
                    # 5.7.x 限制最低版本号为 5.7.31
                    if [ "${tarFileBuildVer}" -ge 31 ] ; then
                        oput green "当前 ${tarFileFullVer} >= 预期 5.7.31 - 已满足条件"
                    else
                        oput red "当前 ${tarFileFullVer} < 预期 5.7.31 - 未满足条件"
                        return 1
                    fi
                    ;;
                * )
                    oput red "脚本只支持 mysql 和 percona" && return 1
                    ;;
            esac
            ;;
        "8.0" )
            case "${tarFileReleaseTag}" in
                "mysql" )
                    # 8.0.x 限制最低版本号为 8.0.11
                    if [ "${tarFileBuildVer}" -ge 11 ] ; then
                        oput green "当前 ${tarFileFullVer} >= 预期 8.0.11 - 已满足条件"
                    else
                        oput red "当前 ${tarFileFullVer} < 预期 8.0.11 - 未满足条件"
                        return 1
                    fi
                    ;;
                "percona" )
                    # 8.0.x 限制最低版本号为 8.0.20
                    if [ "${tarFileBuildVer}" -ge 20 ] ; then
                        oput green "当前 ${tarFileFullVer} >= 预期 8.0.20 - 已满足条件"
                    else
                        oput red "当前 ${tarFileFullVer} < 预期 8.0.20 - 未满足条件"
                        return 1
                    fi
                    ;;
                * )
                    oput red "脚本只支持 mysql 和 percona" && return 1
                    ;;
            esac
            ;;
        "8.4" )
            case "${tarFileReleaseTag}" in
                "mysql" )
                    # 8.4.x 限制最低版本号为 8.4.0
                    if [ "${tarFileBuildVer}" -ge 0 ] ; then
                        oput green "当前 ${tarFileFullVer} >= 预期 8.4.0 - 已满足条件"
                    else
                        oput red "当前 ${tarFileFullVer} < 预期 8.4.0 - 未满足条件"
                        return 1
                    fi
                    ;;
                "percona" )
                    # 8.4.x 限制最低版本号为 8.4.0
                    if [ "${tarFileBuildVer}" -ge 0 ] ; then
                        oput green "当前 ${tarFileFullVer} >= 预期 8.4.0 - 已满足条件"
                    else
                        oput red "当前 ${tarFileFullVer} < 预期 8.4.0 - 未满足条件"
                        return 1
                    fi
                    ;;
                * )
                    oput red "脚本只支持 mysql 和 percona" && return 1
                    ;;
            esac
            ;;
        * )
            oput red "当前 ${tarFileFullVer} != 预期 5.5/5.6/5.7/8.0/8.4 - 未满足条件"
            return 1
            ;;
    esac

    # 限制 内核版本 或 GUN C 库的版本
    case "${tarFileStyle}" in
        "linux" )
            echo -e "内核检查: \c"
            # 如果OS的kernel版本 小于 mysql的kernel版本 则不满足安装条件
            if [ "${tarLinuxMajorVer}" -eq "${osKernelMajorVer}" ] ; then
                if [ "${osKernelMinorVer}" -lt "${tarLinuxMinorVer}" ] ; then
                    oput red "当前 ${tarLinuxFullVer} > 系统 ${osKernelFullVer} - 未满足条件"
                    return 1
                else
                    oput green "当前 ${tarLinuxFullVer} <= 系统 ${osKernelFullVer} - 已满足条件"
                fi
            else
                if [ "${osKernelMajorVer}" -gt "${tarLinuxMajorVer}" ] ; then
                    oput green "当前 ${tarLinuxFullVer} <= 系统 ${osKernelFullVer} - 已满足条件"
                else
                    oput red "当前 ${tarLinuxFullVer} > 系统 ${osKernelFullVer} - 未满足条件"
                    return 1
                fi
            fi
            ;;
        "glibc" )
            echo -e "C 库检查: \c"
            # 如果OS的glibc版本 小于 mysql的glibc版本 则不满足安装条件
            if [ "${tarGlibcMajorVer}" -eq "${osGlibcMajorVer}" ] ; then
                if [ "${osGlibcMinorVer}" -lt "${tarGlibcMinorVer}" ] ; then
                    oput red "当前 ${tarGlibcFullVer} > 系统 ${osGlibcFullVer} - 未满足条件"
                    return 1
                else
                    oput green "当前 ${tarGlibcFullVer} <= 系统 ${osGlibcFullVer} - 已满足条件"
                fi
            else
                oput red "当前 ${tarGlibcFullVer} != 系统 ${osGlibcFullVer} - 未满足条件"
                return 1
            fi
            ;;
        "minimal" )
            echo -e "C 库检查: \c"
            case "${tarFileReleaseTag}" in
                "mysql" )
                    case "${tarFileMajorVer}.${tarFileMinorVer}" in
                        # MySQL 8.0.x 系列 的二进制版（最小安装版）要求最低是 glibc 2.14
                        "8.0" | "8.4" )
                            # 比较浮点数大小
                            if [ "$(echo "${osGlibcFullVer} 2.14" | awk '{if ($1>=$2) {print 0}}')" = "0" ]; then
                                oput green "系统 ${osGlibcFullVer} >= 预期 2.14 - 已满足条件"
                            else
                                oput red "系统 ${osGlibcFullVer} < 预期 2.14 - 未满足条件"
                                return 1
                            fi
                            ;;
                    esac
                    ;;
                "percona" )
                    case "${tarFileMajorVer}.${tarFileMinorVer}" in
                        # percona MySQL 8.0.x 系列 的二进制版（最小安装版）要求最低是 glibc 2.12
                        "8.0" | "8.4" )
                            # 比较浮点数大小
                            if [ "$(echo "${osGlibcFullVer} 2.12" | awk '{if ($1>=$2) {print 0}}')" = "0" ]; then
                                oput green "系统 ${osGlibcFullVer} >= 预期 2.12 - 已满足条件"
                            else
                                oput red "系统 ${osGlibcFullVer} < 预期 2.12 - 未满足条件"
                                return 1
                            fi
                            ;;
                    esac
                    ;;
                * )
                    oput red "脚本只支持 mysql 和 percona" && return 1
                    ;;
            esac
            ;;
    esac
}

#
function select_installMode() {
    local isInstalled inputSelect
    if [ -d "${appInstallDir}" ]; then
        isInstalled="yes"
        local xxx yyy
        xxx="$(oput blue "${installedVer}")"
        yyy="$(oput blue "${appInstallDir}")"
        if [ "${installedVer}" = "" ]; then
            echo; echo "已有安装检查: $(oput red "已检测到")$(oput red "同名安装目录") ${yyy}"
        else
            echo; echo "已有安装检查: $(oput red "已检测到版本为") ${xxx} $(oput red "的同名安装目录") ${yyy}"
        fi
        case "${appRunState}" in
            "0" )
                echo "服务状态检查: $(oput red "服务已启动 pid=${appPidNumber}")"
                ;;
            "1" )
                if [ -f "${appSbinFile}" ]; then
                    echo "服务状态检查: $(oput green "服务未启动")"
                else
                    echo "服务状态检查: $(oput yellow "未能判断服务状态 未找到文件 ${appSbinFile}")"
                fi
                ;;
        esac
    else
        isInstalled="no"
    fi

    case "${isInstalled}" in
        "yes" )
            echo
            echo "d = 删除且全新安装 | u = 备份后覆盖安装 | 其他键 = 取消安装并退出"
            echo
            read -rp "请输入你的选择: " inputSelect
            echo

            # 根据输入定义安装模式 new=全新安装 cover=覆盖安装(只更新程序而不删除配置文件)
            case "${inputSelect}" in
                [dD] )
                    installMode="new"
                    oput red "全新安装 ${appName}"
                    ;;
                [uU] )
                    installMode="cover"
                    oput red "覆盖安装 ${appName}"
                    ;;
                * )
                    echo "安装取消 脚本退出"
                    echo && return 1
                    ;;
            esac

            # 当选择删除和更新安装时 都杀掉服务进程
            case "${inputSelect}" in
                [dD] | [uU] )
                    # 杀进程
                    if ! kill_pid; then return 1; fi
                    ;;
            esac
            ;;
        "no" )
            installMode="new"
            ;;
    esac
}

function kill_pid() {
    # 杀进程
    if [ "${appRunState}" = "0" ]; then
        if kill "${appPidNumber}"; then
            echo; echo "kill ${appName}d main process pid ${appPidNumber} succeed"
        else
            echo; echo "kill ${appName}d main process pid ${appPidNumber} failed"
            return 1
        fi
        # pkill mysqld
    fi
    sleep 3
}

# 执行备份
function backup_app() {
    if [ "${appName}" = "" ]; then echo; oput red "执行备份: 失败 未指定软件名" && return 1; fi
    echo; echo -e "执行备份: ${installRootDir}/${appName} \c"
    # 执行备份(解压成功才进行备份)
    if [ ! -d "${installRootDir}/${appName}" ]; then
        oput yellow "失败 目录不存在 无需备份"
        echo && return 1
    else
        echo "使用 $(oput blue "${backupFormat}") 文件格式打包压缩"
    fi

    local backupFile backupCMD showpackCMD
    # 备份文件名
    backupFile="${backupDir}/backup-${appName}-$(date '+%Y%m%d%H%M%S').${backupFormat}"
    case "${backupFormat}" in
        "tgz" )
            # 打包命令
            backupCMD="${packProg} -czvf ${backupFile} -C ${installRootDir} ${appName} &>>${sLogFile}"
            # 查包命令
            showpackCMD="${packProg} -tzf ${backupFile}"
            # 解包命令
            unpackCMD="${packProg} -xzf ${backupFile}"
            ;;
        "rar" )
            # 打包命令
            backupCMD="${packProg} a -ep1 ${backupFile} ${installRootDir}/${appName} &>>${sLogFile}"
            # 查包命令
            showpackCMD="${packProg} v ${backupFile} 或 unrar l ${backupFile}"
            # 解包命令
            unpackCMD="${packProg} x -o+ -inul ${backupFile}"
            ;;
        "zip" )
            # 打包命令
            backupCMD="cd ${installRootDir} && ${packProg} -r ${backupFile} ${appName} &>>${sLogFile}"
            # 查包命令
            showpackCMD="${packProg} -sf ${backupFile} 或 unzip -l ${backupFile}"
            # 解包命令
            unpackCMD="unzip -oq ${backupFile} 或 7za x -y ${backupFile}"
            ;;
        "7z" )
            # 打包命令
            backupCMD="${packProg} a -spf ${backupFile} ${installRootDir}/${appName} &>>${sLogFile}"
            # 查包命令
            showpackCMD="${packProg} l ${backupFile}"
            # 解包命令
            unpackCMD="${packProg} x -y ${backupFile}"
            ;;
    esac
    echo "备份提示: $(oput blue "如备份耗时过长 可手动备份后 关闭脚本的自动备份再运行脚本")"
    echo -e "打包命令: $(oput blue "${backupCMD}")"
    echo -e "执行备份: \c"
    if eval "${backupCMD}" &>>"${sLogFile}"; then
        oput green "成功 文件: ${backupFile} 大小: $(du -sh "${backupFile}" | awk '{print $1}')"
        echo "查看命令: $(oput blue "${showpackCMD}")"
        echo "解压命令: $(oput blue "${unpackCMD}")"
    else
        oput red "失败 日志文件: ${sLogFile}"
        return 1
    fi

    case "${backupFormat}" in
        "zip" )
            # zip 方式备份时 需要切换回脚本文件所在目录
            if ! cd "${sDir}"; then
                echo "目录切换: 失败 ${sDir}" && return 1
            fi
            ;;
    esac
}

function msg_depend() {
    cat <<EOF

依赖安装参考
  RedHat 5.x: yum install -y iproute ncurses perl libaio util-linux tar gzip numactl
  RedHat 6.x: yum install -y iproute ncurses-libs perl libaio util-linux-ng tar gzip numactl
  RedHat 7.x: yum install -y iproute ncurses-libs perl perl-Data-Dumper libaio util-linux tar gzip numactl libatomic
  RedHat 8.x: dnf install -y iproute perl perl-Data-Dumper libaio util-linux tar gzip numactl libatomic ncurses-compat-libs
  RedHat 9.x: dnf install -y iproute perl perl-Data-Dumper libaio util-linux tar gzip numactl libatomic libxcrypt-compat
  Fedora 23-29: dnf install -y iproute perl-Data-Dumper perl-Getopt-Long libaio gzip numactl-libs
  Fedora 30+: dnf install -y iproute perl-Data-Dumper perl-Getopt-Long libaio gzip numactl-libs compat-openssl10
  Suse 11: zypper in -y iproute2 libncurses5 util-linux perl libaio gzip numactl
  Suse 12+: zypper in -y iproute2 libncurses5 util-linux perl libaio1 gzip numactl libatomic1
  Suse Leap 15+: zypper in -y iproute2 util-linux perl libaio1 gzip numactl
  Debian/UOS: apt install -y iproute2 libncurses5 util-linux perl libaio1 gzip libnuma1 libatomic1
  Ubuntu/UKylin/Deepin: apt install -y iproute2 libncurses5 util-linux perl libaio1 gzip numactl libatomic1
  kylin: dnf install -y iproute perl perl-Data-Dumper libaio util-linux tar gzip numactl libatomic ncurses-compat-libs
  Openeuler: dnf install -y iproute perl perl-Data-Dumper libaio util-linux tar gzip numactl libatomic ncurses-compat-libs
其他一些情况
  报错: error while loading shared libraries: libtinfo.so.5: cannot open shared object file: No such file or directory
  解决: 执行 find / -name libncurses.so* 根据查找结果创建 libncurses.so.x.x 到同目录下 libncurses.so.5 的软连接
  例如: cd /usr/lib64 && ln -s libncurses.so.6.2 libncurses.so.5 && ln -s libtinfo.so.6.2 libtinfo.so.5
EOF
}

# 成功安装后的提示信息
function msg_OK() {
    echo
    echo "安装信息"
    echo "安装路径: ${appInstallDir}"
    echo "当前版本: ${tarFileFullVer}"
    echo "root密码: ${dbRandPWD}"
    echo "远程登录: 已配置 root 账号可以从任意主机上登录"
    case "${tarFileMajorVer}.${tarFileMinorVer}" in
        8.[0-1] )
            echo "登录认证: 已配置 root 账号认证为 ${rootAuthPlugin}"
            ;;
    esac
    echo "连接命令: ${appBinFile} -h${hostAddr} -P${dbPort} -uroot -p${dbRandPWD}"

    local sqlChangePWD
    case "${tarFileMajorVer}.${tarFileMinorVer}" in
        "5.5" | "5.6" | "5.7" )
            sqlChangePWD="set password for 'root'@'%' = password('i4Seeyon'); flush privileges;"
            ;;
        8.[0-9] )
            # sqlChangePWD="alter user 'root'@'%' identified with mysql_native_password by 'i4Seeyon'; flush privileges;"
            sqlChangePWD="alter user 'root'@'%' identified with ${rootAuthPlugin} by 'i4Seeyon'; flush privileges;"
            ;;
    esac
    echo "修改密码: ${sqlChangePWD}"
    echo
    echo "命令参考"
    case "${initIs}" in
        "systemd" )
            echo "启动服务: systemctl start mysqld"
            echo "关闭服务: systemctl stop mysqld"
            echo "重启服务: systemctl restart mysqld"
            echo "服务状态: systemctl status mysqld"
            ;;
        * )
            echo "启动服务: ${appSrvFile} start"
            echo "关闭服务: ${appSrvFile} stop"
            echo "重启服务: ${appSrvFile} restart"
            echo "服务状态: ${appSrvFile} status"
            ;;
    esac
    echo
}

# 安装软件
function install_app() {
    # 软件名检查
    if [ "${appName}" = "" ]; then echo; echo "未设置软件名 不能执行安装" && return 1; fi

    # 目录切换
    if ! cd "${sDir}" 2>>"${sLogFile}"; then echo; echo "目录切换: 失败 ${sDir}" && return 1; fi

    echo
    # echo "解压文件(${appName})"
    if ! unpack_tarFile; then return 1; fi

    if [ "${enableBackup}" = "1" ]; then
        if [ -d "${appInstallDir}" ]; then
            if ! backup_app; then return 1; fi
        fi
    fi

    echo
    echo -e "删除目录: ${appInstallDir} \c"
    if rm -rf "${appInstallDir}"; then
        oput green "成功"
    else
        oput red "失败"
        return 1
    fi
    # do not prompt before overwriting
    echo -e "移动目录: ${installRootDir}/${unpackDir} to ${appInstallDir} \c"
    if mv "${installRootDir}/${unpackDir}" "${appInstallDir}"; then
        oput green "成功"
    else
        oput red "失败"
        return 1
    fi

    # 系统组和用户
    echo; if ! create_groupAndUser; then return 1; fi

    # 安全加固要求有mysql日志记录
    if [ ! -d "${appLogDir}" ]; then mkdir -p "${appLogDir}"; fi
    if [ ! -f "${appErrLogFile}" ]; then touch "${appErrLogFile}"; fi

    echo -e "目录属主: chown -R mysql:mysql ${appInstallDir} \c"
    if chown -R mysql:mysql "${appInstallDir}" ; then oput green "设置成功"; else oput red "设置失败" && return 1; fi
    echo -e "目录权限: chmod -R 755 ${appInstallDir} \c"
    # 如果设置为 750 其他用户不能访问
    if chmod -R 755 "${appInstallDir}"; then oput green "设置成功"; else oput red "设置失败" && return 1; fi

    # 设置参数
    echo; if ! set_appPara; then exit; fi

    # 环境变量
    echo; set_env_var

    # 创建服务控制文件
    echo; create_sysSrvFile

    # 配置开机自启动
    echo; set_auto_start

    action="start"; if ! manage_srv; then return 1; fi

    local localConnStrs dbSetRootStrs
    localConnStrs="${appBinFile} -h127.0.0.1 -P${dbPort}"

    dbRandPWD="$(export LC_ALL=C; tr -cd 0-9a-zA-Z < /dev/urandom | head -c "${dbPWDDigit}")"
    echo -e "随机密码: \c"
    if [ "${dbRandPWD}" = "" ]; then oput red "生成失败" && return 1; else oput green "已生成"; fi

    case "${tarFileMajorVer}.${tarFileMinorVer}" in
        5.[5-6] )
            # 安装 mysql 5.5.x/5.6.x 的代码段
            dbSetRootStrs="${localConnStrs} -e \
                \"use mysql; \
                update user set password=PASSWORD('${dbRandPWD}') where host='localhost' and user='root'; \
                delete from user where password =''; \
                update user set host='%' where host='localhost' and user='root'; \
                flush privileges;\""
            ;;
        "5.7" )
            # 安装 mysql 5.7.x 的代码段
            dbSetRootStrs="${localConnStrs} -e \
                \"use mysql; \
                update mysql.user set authentication_string=password('${dbRandPWD}') \\
                   where user='root' and host='localhost'; \
                update user set host='%' where host='localhost' and user='root'; \
                flush privileges;\""
            ;;
        "8.0" | "8.4" )
            # 安装 mysql 8.0.x/8.4.x 的代码段
            # 设置root帐户的身份认证方式 这个设置不受配置文件的影响 可按需修改 这里设置的目的是为了兼容性
            dbSetRootStrs="${localConnStrs} -e \
                \"use mysql;\
                alter user 'root'@'localhost' identified with '${rootAuthPlugin}' by '${dbRandPWD}';\
                update user set host='%' where user='root';\
                flush privileges;\""
            ;;
    esac

    echo -e "设置root: \c"
    if eval "${dbSetRootStrs}" &>>"${sLogFile}"; then
        oput green "设置完成"
    else
        oput red "设置失败"
        return 1
    fi

    action="restart"; if ! manage_srv; then return 1; fi

    local remoteConnStrs selectVerStr createDBStrs
    remoteConnStrs="${appBinFile} -h${hostAddr} -P${dbPort} -uroot -p${dbRandPWD}"
    echo -e "连接测试: \c"
    # 判断MySQL服务是否正常
    if eval "${appAdminBinFile}" -h"${hostAddr}" -uroot -p"${dbRandPWD}" ping &>>"${sLogFile}"; then
        oput green "已通过"
        selectVerStr="select now(),user(),version();"
        echo -e "语句测试: \c"
        if eval "${remoteConnStrs}" -e "\"${selectVerStr}\"" &>>"${sLogFile}"; then
            oput green "已通过"
        else
            oput red "未通过"
            return 1
        fi
    else
        echo "失败 命令执行异常"
        return 1
    fi

    if [ "${createDBName}" != "" ]; then
#        case "${dbCharacter}" in
#            "utf8" | "utf8mb3" )
#                ;;
#            "utf8mb4" )
#                ;;
#        esac

        createDBStrs="create database if not exists ${createDBName} default character set ${dbCharacter} default collate ${dbCharacter}_general_ci;"
        echo "建库语句: $(oput blue "${createDBStrs}")"
        echo -e "正在建库: ${createDBName} \c"
        if eval "${remoteConnStrs}" -e "\"${createDBStrs}\"" &>>"${sLogFile}"; then
            oput green "成功"
        else
            oput red "失败"
            return 1
        fi
    fi

    # 关闭 mysql 服务
    if [ "${startAppSrv}" != "1" ]; then
        action="stop"; if ! manage_srv; then return 1; fi
    fi

    case "${firewallMethod}" in
        "add" | "dis" )
            echo; set_firewall
            ;;
        * )
            ;;
    esac

    local dbInfoFile
    dbInfoFile="${sDir}/db-mysql-${tarFileMajorVer}.${tarFileMinorVer}.${tarFileBuildVer}.info"
    echo; echo "创建文件: $(oput blue "${dbInfoFile}")"
    msg_OK | tee "${dbInfoFile}"

    case "${tarFileMajorVer}.${tarFileMinorVer}" in
        5.[5-6] )
            oput green "MySQL安装完成 记得按需调整运行参数: innodb_buffer_pool_size 和 innodb_additional_mem_pool_size"
            ;;
        "5.7" | "8.0" | "8.4" )
            oput green "MySQL安装完成 记得按需调整运行参数: innodb_buffer_pool_size"
            ;;
    esac
    # innodb_buffer_pool_size 调优计算方法参考：
    # val = Innodb_buffer_pool_pages_data / Innodb_buffer_pool_pages_total * 100%
    # val > 95% 则考虑增大 建议使用物理内存的75%
    # val < 95% 则考虑减小 建议设置为：Innodb_buffer_pool_pages_data * Innodb_page_size * 1.05 / (1024*1024*1024)
    # 设置命令：set global innodb_buffer_pool_size = 2097152; // 缓冲池字节大小 单位kb 默认128M
}

# 升级
function update_app() {
    if [ "${appName}" = "" ]; then echo; echo "未设置软件名 不能执行安装" && return 1; fi
    if ! cd "${sDir}" 2>>"${sLogFile}"; then echo; echo "目录切换: 失败 ${sDir}" && return 1; fi

    echo; echo -e "版本比较: \c"
    if [ "${tarFileMajorVer}.${tarFileMinorVer}" != "${installedMajorVer}.${installedMinorVer}" ]; then
        oput red "待装/已装 的主次版本号未匹配 ${tarFileFullVer} != ${installedVer}"
        echo "若需跨大版本升级 请参照官方文档手动操作 $(oput blue "https://dev.mysql.com/doc")"
        return 1
    else
        local tempStrs="${tarFileMajorVer}.${tarFileMinorVer} = ${installedMajorVer}.${installedMinorVer}"
        oput green "待装/已装 的主次版本号已匹配 ${tempStrs}"
        echo -e "版本比较: \c"
        # 如果要允许降级覆盖安装 可以注释后续条件判断
        if [ "${tarFileBuildVer}" -lt "${installedBuildVer}" ]; then
            oput red "待装/已装 的版本构建号未满足 当前 ${tarFileFullVer} < ${installedVer}"
            return 1
        else
            oput green "待装/已装 的版本构建号已满足 ${tarFileBuildVer} >= ${installedBuildVer}"
        fi
    fi

    echo
    # echo "解压文件(${appName})"
    if ! unpack_tarFile; then return 1; fi
    # 覆盖安装的情况下 检测二进制包是不是存在 data/ my.cnf support-files/mysql.server 如果存在则删除
    local lists=(
        "${installRootDir}/${unpackDir}/data"
        "${installRootDir}/${unpackDir}/my.cnf"
        "${installRootDir}/${unpackDir}/support-files/mysql.server"
        )
    for x in "${lists[@]}"; do
        if [ -d "${x}" ]; then
            echo "删除目录: $(oput blue "${x}")"
            rm -rf "${x}"
        elif [ -f "${x}" ]; then
            echo "删除文件: $(oput blue "${x}")"
            rm -f "${x}"
        fi
    done

    if [ "${enableBackup}" = "1" ]; then
        if [ -d "${appInstallDir}" ]; then
            if ! backup_app; then return 1; fi
        fi
    fi

    # do not prompt before overwriting
    echo
    echo "复制文件: ${installRootDir}/${unpackDir}/* to ${appInstallDir}/"
    alias cp='cp'
    local cpCMD
    cpCMD="cp -af ${installRootDir}/${unpackDir}/* ${appInstallDir}/"
    echo -e "执行命令: ${cpCMD} \c"
    if eval "${cpCMD}"; then oput green "成功"; else oput red "失败" && return 1; fi

    echo "删除目录: ${installRootDir}/${unpackDir}"
    rm -rf "${installRootDir:?parameter null or not set}/${unpackDir:?parameter null or not set}"

    echo -e "设置属主: chown -R mysql:mysql ${appInstallDir} \c"
    if chown -R mysql:mysql "${appInstallDir}" ; then oput green "成功"; else oput red "失败" && return 1; fi
    echo -e "设置权限: chmod -R 755 ${appInstallDir} \c"
    if chmod -R 755 "${appInstallDir}"; then oput green "成功"; else oput red "失败" && return 1; fi

    # 启动 mysql 服务
    action="start"; if ! manage_srv; then return 1; fi

    local needExecUpgrade upgradeCMD
    case "${installedMajorVer}.${installedMinorVer}" in
        5.[567] )
            # 当 安装包的 Major.Minor > 原有版本 Major.Minor 时 判断是否需要执行 mysql_upgrade
            if [ "${tarFileBuildVer}" -gt "${installedBuildVer}" ]; then needExecUpgrade="1"; fi
            ;;
        8.0 )
            # 当 安装包的 Major.Minor > 原有版本 Major.Minor 时 判断是否需要执行 mysql_upgrade
            if [ "${tarFileBuildVer}" -lt 16 ]; then needExecUpgrade="1"; fi
            ;;
    esac

    case "${needExecUpgrade}" in
        "1" )
            # 需要执行 mysql_upgrade 时
            local inputRootPWD
            echo
            read -rp "密码请求: 请输入数据库升级前的 $(oput blue "root") 账号的密码: " inputRootPWD
            upgradeCMD="${appUpgradeFile} -h${hostAddr} -P${dbPort} -uroot -p"
            if [ "${inputRootPWD}" = "" ]; then
                oput red "密码请求: 密码为空 请手动执行以下命令完成最后一步操作"
                oput blue "${upgradeCMD}"
                echo
            else
                echo -e "执行命令: ${upgradeCMD}${inputRootPWD} \c"
                if eval "${upgradeCMD}${inputRootPWD}" &>>"${sLogFile}"; then
                    oput green "成功"
                    echo; oput green "已完成 覆盖/升级 安装"; echo
                else
                    oput red "失败 请手动执行以下命令完成最后一步操作"
                    oput blue "${upgradeCMD}"
                    echo
                fi
            fi
            ;;
        * )
            # 不需要执行 mysql_upgrade 时
            echo; oput green "已完成 覆盖/升级 安装"
            ;;
    esac
}

# 移除
function remove_app() {
    # 执行删除
    oput red "卸载 ${appName}"
    if ! kill_pid; then exit; fi
    # 防止误删 根据脚本参数设置 启用删除前的自动备份
    if [ "${enableBackup}" = "1" ]; then
        if ! backup_app; then return 1; fi
    fi

    echo; set_auto_start
    echo; set_env_var

    echo; echo -e "删除目录: ${appInstallDir} \c"
    if rm -rf "${appInstallDir}"; then oput green "成功"; else oput red "失败" && return 1; fi
}

# 打印主机相关信息
function print_host_info() {
    echo "登录信息: $(id -un)@${hostAddr} from ${clientAddr}"
    echo "主机名称: ${hostName}"
    echo "CPU 型号: ${cpuModel}"
    echo "硬件概览: ${cpuLogic} 核CPU + ${memTotal} GB内存 + ${diskTotalSize:=0} GB磁盘"
    echo "操作系统: ${osRelease}"
    echo "系统内核: ${osKernelFullVer}"
    echo "GNU C 库: ${osGlibcFullVer}"
    echo "Bash版本: ${BASH_VERSION}"
}

# 打印脚本信息
function print_scriptInfo() {
    echo
    echo "脚本文件: ${sFile}"
    echo "脚本功能: ${sDesc}"
    echo "环境支持: ${sEnv}"
    echo "脚本用法: ${sUsage}"
    echo "更新时间: ${sUpdate}"
    echo
    print_host_info

    echo
    echo -e "已装检查: \c"
    if [ "${installedVer}" != "" ]; then
        oput red "已检测到 ver=${installedVer}"
    else
        oput green "未检测到"
    fi
    echo -e "进程检查: \c"
    if [ "${appRunState}" = "0" ]; then
        oput red "已检测到 pid=${appPidNumber}"
    else
        oput green "未检测到"
    fi
    echo -e "监听端口: \c"
    if [ "${appPortState}" = "0" ]; then
        oput red "已被占用 port=${dbPort}"
    else
        oput green "未被占用 port=${dbPort}"
    fi
    echo -e "二进制包: \c"
    if [ "${tarFile}" != "" ]; then
        oput green "已找到 ${tarFile} - ${tarFileSize}"
    else
        echo "$(oput red "未找到 请将从官网下载的") $(oput blue "${archType}") $(oput red "平台架构的二进制安装包与脚本放在一起")"
        echo "查找命令: ${findCMDStr}"
    fi
    echo -e "安装目录: \c"
    if [ "${installRootDir}" != "" ]; then
        oput green "${installRootDir}/mysql"
    else
        oput red "未设定安装根目录"
    fi
    echo "备份格式: $(oput green "${backupFormat}")"
}

# 未传参时的脚本提示
function main_prompt() {
    # 输出信息提示
    cat <<EOF

版本支持情况
  Official: 5.5.x >= 5.5.8 | 5.6.x >= 5.6.10 | 5.7.x >= 5.7.9 | 8.0.x >= 8.0.11 | 8.4.x >= 8.4.0
  Percona : 5.7.x >= 5.7.31 | 8.0.x >= 8.0.20 | 8.4.x >= 8.4.0
脚本使用说明
  只支持 AMD64/ARM64 的操作系统以及官方的二进制安装包
  脚本和 MySQL 二进制包放在同级目录 终端下传参执行脚本
脚本相关命令
  安装: bash ${sFile} install
  卸载: bash ${sFile} remove
  备份: bash ${sFile} backup
  依赖: bash ${sFile} depend
服务启停管理
  systemd: systemctl start|restart|stop|status mysqld
  manual : ${installRootDir}/mysql/support-files/mysql.server start|restart|stop|status
官方下载地址
  $(oput blue "mysql official")
  5.5.x: https://dev.mysql.com/downloads/mysql/5.5.html
  5.6.x: https://dev.mysql.com/downloads/mysql/5.6.html
  5.7.x: https://dev.mysql.com/downloads/mysql/5.7.html
  8.0.x: https://dev.mysql.com/downloads/mysql/8.0.html
  8.4.x: https://dev.mysql.com/downloads/mysql/8.4.html
  $(oput blue "percona server for mysql")
  5.5.x: https://www.percona.com/downloads/Percona-Server-5.5/
  5.6.x: https://www.percona.com/downloads/Percona-Server-5.6/
  5.7.x: https://www.percona.com/downloads/Percona-Server-5.7/
  8.0.x: https://www.percona.com/downloads/Percona-Server-8.0/
  8.4.x: https://www.percona.com/downloads/Percona-Server-8.4/
EOF

echo; oput red "安装前记得先执行依赖检查和安装"
}

appName="mysql"

get_systemInfo
get_installedVersion
check_pidAndPort
search_tarFile
print_scriptInfo

if ! verify_scriptPara; then exit; fi
# 载入系统环境
if [ "${loadEtcProfile}" = "1" ]; then source /etc/profile; fi
if [ "${loadsBinDir}" = "1" ]; then
    if [ -d "${sBinDir}" ]; then
        chmod -R +x "${sBinDir}"
        case "${archType}" in
            "amd64" | "x86_64" )
                export PATH=${sBinDir}/amd64:$PATH
                ;;
            "arm64" | "aarch64" )
                export PATH=${sBinDir}/arm64:$PATH
                ;;
        esac
    fi
fi

if ! check_CMD; then exit; fi

case "$1" in
    "install" )
        # w=write
        setEnvAction="w"; setAutoStartAction="w"
        if [ "${tarFile}" = "" ]; then echo; exit; fi
        if ! check_versionLimit; then echo; exit; fi
        echo; print_parameter_info
        case "$(hostname)" in
            "localhost" | "localhost.localdomain" )
                echo; echo "$(oput red "主机名") 不能使用默认的 $(oput red "$(hostname)") 请修改后再安装"
                echo; exit
        esac
        if ! select_installMode; then exit; fi
        case "${installMode}" in
            "new" )
                disable_selinux
                if ! install_app 2>&1 | tee "${sLogFile}"; then exit; fi
                ;;
            "cover" )
                if ! update_app 2>&1 | tee "${sLogFile}"; then exit; fi
        esac
        echo; echo "脚本运行日志: ${sLogFile}"
        ;;
    "remove" )
        # d=delete
        setEnvAction="d"; setAutoStartAction="d"
        if [ -d "${appInstallDir}" ]; then
            echo; remove_app
        else
            echo; oput red "卸载失败: 目录不存在 ${appInstallDir}"
        fi
        ;;
    "backup" )
        if [ -d "${appInstallDir}" ]; then
            if ! kill_pid; then exit; fi
            backup_app
        else
            echo; echo "备份失败: 目录不存在 ${appInstallDir}"
        fi
        ;;
    "depend" )
        msg_depend
        ;;
    * )
        main_prompt
        ;;
esac

echo
